package cn.nubia.admin.common.ftp;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 非线程安全,请使用FtpClientUtil
 * Created by Administrator on 2014/4/2.
 */
public class FTPClientWrapper {
    private static final Logger logger = LoggerFactory.getLogger(FTPClientWrapper.class);
    public static final String SLASH = "/";
    public static final String ISO_8859_1 = "iso-8859-1";
    public static final String UTF_8 = "utf-8";


    private final FTPClient ftpClient = new FTPClient();



    private String ftpServer;
    private String username;
    private String password;
    private Boolean localActive = true;
    private Boolean binaryTransfer = false;

    private String serverCharset = ISO_8859_1;



    /**
     * 构造ftp封装器
     *
     * @param ftpServer
     * @param username
     * @param password
     */
    public FTPClientWrapper(String ftpServer, String username, String password) {
        this(ftpServer, username, password, true, true);
    }

    /**
     * 构造ftp封装器
     *
     * @param ftpServer      服务器地址
     * @param username       用户名
     * @param password       密码
     * @param localActive    是否本地
     * @param binaryTransfer 传输模式
     */
    public FTPClientWrapper(String ftpServer, String username, String password, Boolean localActive, Boolean binaryTransfer) {
        this.ftpServer = ftpServer;
        this.username = username;
        this.password = password;
        this.localActive = localActive;
        this.binaryTransfer = binaryTransfer;

        if (logger.isInfoEnabled()) {
            logger.info("ftp url:"+ftpServer+",u:"+username+",p:"+password);
        }
        connectServerIfNeed();
    }

    public List<FtpFileItem> getFileItems(String dir) {
        connectServerIfNeed();
        List<FtpFileItem> list = new ArrayList<FtpFileItem>();

        for (FTPFile ftpFile : getFiles(dir)) {
            list.add(new FtpFileItem(toLocalCode(ftpFile.getName()), ftpFile.getTimestamp().getTime(), ftpFile.getType()));
        }

        return list;
    }

    private List<FTPFile> getFiles(String dir) {
        try {
            List<FTPFile> files = new ArrayList<FTPFile>();
            changeWorkDir(dir);


            Collections.addAll(files, getFtpClient().listFiles());
            return files;
        } catch (IOException e) {
            logger.error("get files error", e);
        }
        return Collections.emptyList();
    }

    /**
     * 用流的注意了..使用的时候自己记得关掉
     *
     * @param dir      工作目录
     * @param filename 文件名
     * @param is       流
     */
    public Boolean addFile(String dir, String filename, InputStream is) {
        try {
            String filePath = FtpHelper.generatePathWithFilename(filename, dir);
            changeWorkDir(dir);
            boolean isSuc = getFtpClient().storeFile(toServerCode(filename), is);
            logger.info("store file:" + filePath + ",suc:" + isSuc);
            return isSuc;
        } catch (IOException e) {
            logger.error("add file error", e);
        }
        return false;
    }

    public Boolean addFile(String dir, String filename, File file) {
        try {
            InputStream is = FileUtils.openInputStream(file);
            boolean isSuc = addFile(dir, filename, is);
            is.close();
            return isSuc;
        } catch (IOException e) {
            logger.error("add file error", e);
        }
        return false;
    }

    /**
     * 判断目录是否存在
     * @param dir
     * @return
     */
    public Boolean isDirExist(String dir) {
        connectServerIfNeed();
        try {
            return changeWorkDir(dir);
        }catch (Exception e) {
            return false;
        }
    }

    /**
     * 删除文件
     *
     * @param dir
     * @param filename
     */
    public Boolean deleteFile(String dir, String filename) {
        try {
            changeWorkDir(dir);
            return getFtpClient().deleteFile(toServerCode(filename));
        } catch (IOException e) {
            logger.error("del file error", e);
        }
        return false;
    }

    public  void shutdown() {
        if (ftpClient.isConnected()) {
            try {
                ftpClient.logout();
                ftpClient.disconnect();
            } catch (IOException e) {
                logger.error("disconnect ftp server error!", e);
            }
        }
    }

    public File getFile(String dir, String filename, File file) {
        try {
            OutputStream outputStream = FileUtils.openOutputStream(file);
            changeWorkDir(dir);
            boolean suc = getFtpClient().retrieveFile(toServerCode(filename), outputStream);
            outputStream.close();
            if (suc) {
                return file;
            }
        } catch (IOException e) {
            logger.error("get file error:" + dir + filename, e);
        }
        return null;
    }

    /**
     * 获取文件路径内容
     *
     * @param dir
     * @param filename
     * @return
     */
    public String getFileContent(String dir, String filename , String encode) {
        connectServerIfNeed();
        try {
            File file = File.createTempFile("cms_tmp_ftp_", ".data");
            File tmpFile = getFile(dir, filename, file);
            if (tmpFile == null) {
                return null;
            }

            return FileUtils.readFileToString(tmpFile , encode);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 创建目录..需要父级目录存在
     *
     * @param workDir
     * @param newDirName
     */
    public Boolean createDir(String workDir, String newDirName) {
        connectServerIfNeed();
        try {
            changeWorkDir(workDir);
            boolean suc = getFtpClient().makeDirectory(toServerCode(newDirName));
            if (!suc) {
                logger.debug("create dir failed:" + newDirName);
            }
            return suc;
        } catch (IOException e) {
            logger.error("get file error:" + newDirName, e);
        }
        return false;
    }



    /**
     * 创建目录,如果父级目录不存在则创建
     *
     * @param path
     */
    public Boolean createDirs(String path) {
        try {
            changeWorkDir(SLASH);

            StringBuilder sb = new StringBuilder(SLASH);
            for (String part : path.split(SLASH)) {
                if (StringUtils.isEmpty(part)) {
                    continue;
                }

                sb.append(part).append(SLASH);
                boolean suc = getFtpClient().makeDirectory(toServerCode(sb.toString()));
                if (!suc) {
                    logger.error("dir is exist");
                }

                if (logger.isInfoEnabled()) {
                    logger.info("create dir :" + sb.toString());
                }
            }
            return true;
        } catch (IOException e) {
            logger.error("create dirs error:" + path);
            return false;
        }
    }

    /**
     * 移除目录
     *
     * @param path
     */
    public Boolean deleteDir(String path) {
        try {
            changeWorkDir(SLASH);
            boolean suc = getFtpClient().removeDirectory(toServerCode(path));
            if (!suc) {
                logger.error("delete dir failed");
            }
            return suc;
        } catch (IOException e) {
            logger.error("create dirs error:" + path, e);
        }
        return false;
    }

    public Boolean renameFile(String originalPath , String newPath) {

        try {
            getFtpClient().rename(toServerCode(originalPath), toServerCode(newPath));
            return true;
        }catch (IOException e) {
            logger.error("rename files error ,path:" + originalPath+",new path:"+newPath, e);
            return false;
        }
    }


    public void setLocalActive(Boolean localActive) {
        this.localActive = localActive;
    }

    public void setBinaryTransfer(Boolean binaryTransfer) {
        this.binaryTransfer = binaryTransfer;
    }

    public FTPClient getFtpClient() {
        connectServerIfNeed();
        return ftpClient;
    }

    private void connectServerIfNeed()  {
        try {
            if (ftpClient.isConnected()) {
                try {
                    boolean suc = ftpClient.sendNoOp();
                    if (suc) {
                        logger.info("连接未中断,发送no op指令成功");
                        return;
                    }
                }catch (Exception e) {
                    logger.error("send no op error!", e);
                    try {
                        ftpClient.disconnect();
                    }catch (IOException ex) {
                        logger.error("disconnect error" , ex);
                    }
                }

            }


            logger.warn("连接已断开,正在连接远程ftp服务器");

            ftpClient.connect(ftpServer);
            ftpClient.login(username, password);
            ftpClient.setFileType(FTP.ASCII_FILE_TYPE);
//            if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {
//                // 发送OPTS UTF8指令，尝试支持utf8
//                serverCharset = UTF_8;
//            }
//            System.out.println("char set is:"+serverCharset);
//            ftpClient.setControlEncoding(serverCharset);
            if (localActive) {
                ftpClient.enterLocalActiveMode();
            } else {
                ftpClient.enterLocalPassiveMode();
            }
            if (binaryTransfer) {
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            } else {
                // in theory this should not be necessary as servers should default to ASCII
                // but they don't all do so - see NET-500
                ftpClient.setFileType(FTP.ASCII_FILE_TYPE);
            }

        }catch (Exception e) {
            logger.error("ftp connect server error!" , e);
        }
    }

    private Boolean changeWorkDir(String workDir) throws IOException {
        boolean suc = getFtpClient().changeWorkingDirectory(toServerCode(workDir));
        if (!suc) {
            throw new RuntimeException("目录不存在:"+workDir);
        }
        return true;
    }

    private String toServerCode(String workDir) {
        try {
            return new String(workDir.getBytes(UTF_8) ,serverCharset);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return workDir;
    }

    private String toLocalCode(String workDir)  {
        try {
            return new String(workDir.getBytes(serverCharset) ,UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return  workDir;
    }
}
